# -*- coding: utf-8 -*-
#    Copyright 2005 Spike^ekipS <spikeekips@gmail.com>
#
#       This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

"""A generic programming thread pool

Highly inspired from  :
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/203871

Thanks Tim Lesher
"""

import gc
from time import sleep
import threading
import logging

# REMOVE: remove logging facilities.
#from util import logging

logger = logging.getLogger("ThreadPool")

class Thread :

	def __init__ (self, pool_size) :
		# Thread pool
		self._sync = False
		self._pool = None
		self._pool_size = pool_size

	def _getThreadPool(self) :
		if not self._sync:
			if self._pool is None:
				self._pool = ThreadPool(self._pool_size)
		return self._pool

	def getThreadCount (self) :
		if not self._pool :
			return 0
		else :
			return self._pool.getThreadCount()

	def getTaskCount (self) :
		if not self._pool :
			return 0
		else :
			return self._pool.getTaskCount()

	def insertQueue (self, func, args) :
		pool = self._getThreadPool()
		if pool is not None :
			pool.queueTask(func, args, None)
		else:
			func(*args)

		gc.collect()

		return True

class ThreadPool :

	"""Flexible thread pool class.  Creates a pool of threads, then
	accepts tasks that will be dispatched to the next available
	thread."""

	def __init__(self, numThreads) :

		"""Initialize the thread pool with numThreads workers."""

		self.__threads = []
		self.__resizeLock = threading.Condition(threading.Lock())
		self.__taskLock = threading.Condition(threading.Lock())
		self.__tasks = []
		self.__isJoining = False
		self.setThreadCount(numThreads)

	def getTaskCount (self) :
		return len(self.__tasks)

	def setThreadCount(self, newNumThreads) :

		""" External method to set the current pool size.  Acquires
		the resizing lock, then calls the internal version to do real
		work."""

		# Can't change the thread count if we're shutting down the pool!
		if self.__isJoining :
			return False

		self.__resizeLock.acquire()
		try :
			self.__setThreadCountNolock(newNumThreads)
		finally :
			self.__resizeLock.release()
		return True

	def __setThreadCountNolock(self, newNumThreads) :

		"""Set the current pool size, spawning or terminating threads
		if necessary.  Internal use only; assumes the resizing lock is
		held."""

		# If we need to grow the pool, do so
		while newNumThreads > len(self.__threads) :
			newThread = ThreadPoolThread(self)
			self.__threads.append(newThread)
			newThread.start()
		# If we need to shrink the pool, do so
		while newNumThreads < len(self.__threads) :
			self.__threads[0].goAway()
			del self.__threads[0]

	def getThreadCount(self) :

		"""Return the number of threads in the pool."""

		return len(self.__threads)
		self.__resizeLock.acquire()
		try :
			return len(self.__threads)
		finally :
			self.__resizeLock.release()

	def queueTask(self, task, args=None, taskCallback=None) :

		"""Insert a task into the queue.  task must be callable;
		args and taskCallback can be None."""

		logger.debug("ThreadPool -> gc.garbage : %s" % str(gc.garbage))

		if self.__isJoining == True :
			return False
		if not callable(task) :
			return False

		self.__taskLock.acquire()
		try :
			self.__tasks.append((task, args, taskCallback, ))
			return True
		finally :
			self.__taskLock.release()

	def getNextTask(self) :

		""" Retrieve the next task from the task queue.  For use
		only by ThreadPoolThread objects contained in the pool."""

		self.__taskLock.acquire()
		try :
			if self.__tasks == [] :
				return (None, None, None)
			else :
				return self.__tasks.pop(0)
		finally :
			self.__taskLock.release()

	def joinAll(self, waitForTasks = True, waitForThreads = True) :

		""" Clear the task queue and terminate all pooled threads,
		optionally allowing the tasks and threads to finish."""

		# Mark the pool as joining to prevent any more task queueing
		self.__isJoining = True

		# Wait for tasks to finish
		if waitForTasks :
			while self.__tasks != [] :
				sleep(.1)

		# Tell all the threads to quit
		self.__resizeLock.acquire()
		try :
			self.__setThreadCountNolock(0)
			self.__isJoining = True

			# Wait until all threads have exited
			if waitForThreads :
				for t in self.__threads :
					t.join()
					del t

			# Reset the pool for potential reuse
			self.__isJoining = False
		finally :
			self.__resizeLock.release()



"""
Description
-----------


ChangeLog
---------


Usage
-----


"""

__author__ =  "Spike^ekipS <spikeekips@gmail.com>"
__version__=  "0.1"
__nonsense__ = ""

__file__ = "__default__.py"


